using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using AiPlatformAdmin.Api.Models;
using AiPlatformAdmin.Api.Services;
using System.Linq.Expressions;

namespace AiPlatformAdmin.Api.Data;

public class AppDbContext : DbContext
{
    private readonly ITenantService _tenantService;

    public AppDbContext(DbContextOptions<AppDbContext> options, ITenantService tenantService) : base(options)
    {
        _tenantService = tenantService;
    }

    public DbSet<Tenant> Tenants { get; set; }
    public DbSet<User> Users { get; set; }
    public DbSet<Team> Teams { get; set; }
    public DbSet<UserTeam> UserTeams { get; set; }
    public DbSet<TeamAdmin> TeamAdmins { get; set; }
    public DbSet<KnowledgeBase> KnowledgeBases { get; set; }
    public DbSet<KnowledgeBaseAdmin> KnowledgeBaseAdmins { get; set; }
    public DbSet<KnowledgeBaseTeamAccess> KnowledgeBaseTeamAccesses { get; set; }
    public DbSet<Document> Documents { get; set; }
    public DbSet<Agent> Agents { get; set; }
    public DbSet<AgentAdmin> AgentAdmins { get; set; }
    public DbSet<AgentTeamAccess> AgentTeamAccesses { get; set; }
    public DbSet<AgentKnowledgeBase> AgentKnowledgeBases { get; set; }
    public DbSet<Plugin> Plugins { get; set; }
    public DbSet<AgentPlugin> AgentPlugins { get; set; }
    public DbSet<TokenUsage> TokenUsages { get; set; }
    public DbSet<LlmModel> LlmModels { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Configure Tenant entity
        modelBuilder.Entity<Tenant>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.TenantKey).IsUnique();
            entity.HasIndex(e => e.Name);
        });

        // Configure User entity
        modelBuilder.Entity<User>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.Email).IsUnique();
            entity.HasIndex(e => e.Username).IsUnique();
            entity.HasIndex(e => e.TenantId);

            entity.HasOne(u => u.Tenant)
                .WithMany(t => t.Users)
                .HasForeignKey(u => u.TenantId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // Configure Team entity
        modelBuilder.Entity<Team>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.Name);

            entity.HasOne(t => t.Tenant)
                .WithMany(tn => tn.Teams)
                .HasForeignKey(t => t.TenantId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // Configure UserTeam many-to-many relationship
        modelBuilder.Entity<UserTeam>(entity =>
        {
            entity.HasKey(ut => new { ut.UserId, ut.TeamId });

            entity.HasOne(ut => ut.User)
                .WithMany(u => u.UserTeams)
                .HasForeignKey(ut => ut.UserId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(ut => ut.Team)
                .WithMany(t => t.UserTeams)
                .HasForeignKey(ut => ut.TeamId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Configure TeamAdmin many-to-many relationship
        modelBuilder.Entity<TeamAdmin>(entity =>
        {
            entity.HasKey(ta => new { ta.TeamId, ta.UserId });

            entity.HasOne(ta => ta.Team)
                .WithMany(t => t.TeamAdmins)
                .HasForeignKey(ta => ta.TeamId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(ta => ta.User)
                .WithMany(u => u.TeamAdmins)
                .HasForeignKey(ta => ta.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Configure KnowledgeBase entity
        modelBuilder.Entity<KnowledgeBase>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.Name);

            entity.HasOne(kb => kb.Tenant)
                .WithMany(t => t.KnowledgeBases)
                .HasForeignKey(kb => kb.TenantId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // Configure KnowledgeBaseAdmin many-to-many relationship
        modelBuilder.Entity<KnowledgeBaseAdmin>(entity =>
        {
            entity.HasKey(kba => new { kba.KnowledgeBaseId, kba.UserId });

            entity.HasOne(kba => kba.KnowledgeBase)
                .WithMany(kb => kb.KnowledgeBaseAdmins)
                .HasForeignKey(kba => kba.KnowledgeBaseId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(kba => kba.User)
                .WithMany(u => u.KnowledgeBaseAdmins)
                .HasForeignKey(kba => kba.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Configure KnowledgeBaseTeamAccess many-to-many relationship
        modelBuilder.Entity<KnowledgeBaseTeamAccess>(entity =>
        {
            entity.HasKey(kbta => new { kbta.KnowledgeBaseId, kbta.TeamId });

            entity.HasOne(kbta => kbta.KnowledgeBase)
                .WithMany(kb => kb.KnowledgeBaseTeamAccesses)
                .HasForeignKey(kbta => kbta.KnowledgeBaseId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(kbta => kbta.Team)
                .WithMany(t => t.KnowledgeBaseTeamAccesses)
                .HasForeignKey(kbta => kbta.TeamId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Configure Document entity
        modelBuilder.Entity<Document>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.KnowledgeBaseId);

            entity.HasOne(d => d.KnowledgeBase)
                .WithMany(kb => kb.Documents)
                .HasForeignKey(d => d.KnowledgeBaseId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Configure Agent entity
        modelBuilder.Entity<Agent>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.Name);

            entity.HasOne(ve => ve.Tenant)
                .WithMany(t => t.Agents)
                .HasForeignKey(ve => ve.TenantId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // Configure AgentAdmin many-to-many relationship
        modelBuilder.Entity<AgentAdmin>(entity =>
        {
            entity.HasKey(vea => new { vea.AgentId, vea.UserId });

            entity.HasOne(vea => vea.Agent)
                .WithMany(ve => ve.AgentAdmins)
                .HasForeignKey(vea => vea.AgentId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(vea => vea.User)
                .WithMany(u => u.AgentAdmins)
                .HasForeignKey(vea => vea.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Configure AgentTeamAccess many-to-many relationship
        modelBuilder.Entity<AgentTeamAccess>(entity =>
        {
            entity.HasKey(veta => new { veta.AgentId, veta.TeamId });

            entity.HasOne(veta => veta.Agent)
                .WithMany(ve => ve.AgentTeamAccesses)
                .HasForeignKey(veta => veta.AgentId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(veta => veta.Team)
                .WithMany(t => t.AgentTeamAccesses)
                .HasForeignKey(veta => veta.TeamId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Configure AgentKnowledgeBase many-to-many relationship
        modelBuilder.Entity<AgentKnowledgeBase>(entity =>
        {
            entity.HasKey(vekb => new { vekb.AgentId, vekb.KnowledgeBaseId });

            entity.HasOne(vekb => vekb.Agent)
                .WithMany(ve => ve.AgentKnowledgeBases)
                .HasForeignKey(vekb => vekb.AgentId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(vekb => vekb.KnowledgeBase)
                .WithMany(kb => kb.AgentKnowledgeBases)
                .HasForeignKey(vekb => vekb.KnowledgeBaseId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Configure Plugin entity
        modelBuilder.Entity<Plugin>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.Name);

            entity.HasOne(p => p.Tenant)
                .WithMany(t => t.Plugins)
                .HasForeignKey(p => p.TenantId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // Configure AgentPlugin many-to-many relationship
        modelBuilder.Entity<AgentPlugin>(entity =>
        {
            entity.HasKey(vep => new { vep.AgentId, vep.PluginId });

            entity.HasOne(vep => vep.Agent)
                .WithMany(ve => ve.AgentPlugins)
                .HasForeignKey(vep => vep.AgentId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(vep => vep.Plugin)
                .WithMany(p => p.AgentPlugins)
                .HasForeignKey(vep => vep.PluginId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Configure TokenUsage entity
        modelBuilder.Entity<TokenUsage>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => new { e.UserId, e.Timestamp });
            entity.HasIndex(e => new { e.AgentId, e.Timestamp });

            entity.HasOne(tu => tu.User)
                .WithMany(u => u.TokenUsages)
                .HasForeignKey(tu => tu.UserId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(tu => tu.Agent)
                .WithMany(ve => ve.TokenUsages)
                .HasForeignKey(tu => tu.AgentId)
                .OnDelete(DeleteBehavior.Cascade);

            entity.HasOne(tu => tu.Tenant)
                .WithMany()
                .HasForeignKey(tu => tu.TenantId)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // Configure LlmModel entity
        modelBuilder.Entity<LlmModel>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.HasIndex(e => e.ModelId);
            entity.HasIndex(e => e.Name);
            entity.HasIndex(e => new { e.TenantId, e.Provider });
            entity.Property(e => e.Category).HasConversion<string>();

            entity.HasOne(lm => lm.Tenant)
                .WithMany()
                .HasForeignKey(lm => lm.TenantId)
                .OnDelete(DeleteBehavior.Restrict);

            entity.HasOne(lm => lm.CreatedBy)
                .WithMany()
                .HasForeignKey(lm => lm.CreatedById)
                .OnDelete(DeleteBehavior.Restrict);
        });

        // Apply global query filter for multi-tenancy
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            if (typeof(ITenantEntity).IsAssignableFrom(entityType.ClrType))
            {
                var method = typeof(AppDbContext)
                    .GetMethod(nameof(SetGlobalQueryFilter), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
                    ?.MakeGenericMethod(entityType.ClrType);

                method?.Invoke(this, new object[] { modelBuilder });
            }
        }
    }

    private int? CurrentTenantId => _tenantService.CurrentTenantId;

    private void SetGlobalQueryFilter<TEntity>(ModelBuilder modelBuilder) where TEntity : class, ITenantEntity
    {
        modelBuilder.Entity<TEntity>().HasQueryFilter(e => !CurrentTenantId.HasValue || e.TenantId == CurrentTenantId.Value);
    }

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        ApplyAuditInfo();
        return base.SaveChanges(acceptAllChangesOnSuccess);
    }

    public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
    {
        ApplyAuditInfo();
        return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    }

    private void ApplyAuditInfo()
    {
        var utcNow = DateTime.UtcNow;
        var tenantId = CurrentTenantId;

        foreach (var entry in ChangeTracker.Entries())
        {
            if (entry.State == EntityState.Added)
            {
                if (tenantId.HasValue && entry.Entity is ITenantEntity tenantEntity && tenantEntity.TenantId == 0)
                {
                    tenantEntity.TenantId = tenantId.Value;
                }

                SetDateTimeProperty(entry, "CreatedAt", utcNow, overwriteExisting: false);
                SetDateTimeProperty(entry, "UpdatedAt", utcNow, overwriteExisting: true);
            }
            else if (entry.State == EntityState.Modified)
            {
                SetDateTimeProperty(entry, "UpdatedAt", utcNow, overwriteExisting: true);
            }
        }
    }

    private static void SetDateTimeProperty(EntityEntry entry, string propertyName, DateTime value, bool overwriteExisting)
    {
        var property = entry.Properties.FirstOrDefault(p => string.Equals(p.Metadata.Name, propertyName, StringComparison.Ordinal));
        if (property == null)
        {
            return;
        }

        if (!overwriteExisting && property.CurrentValue is DateTime existingValue && existingValue != default)
        {
            return;
        }

        property.CurrentValue = value;
    }
}
